//
//  subgradient_horizon.h
//  distributed-gradient
//
//  Created by Emanuele Vespa on 6/28/12.
//  Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
//
//  subradient_sim.h
//  distributed-gradient
//
//  Created by Emanuele Vespa on 6/21/12.
//  Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//

#ifndef distributed_gradient_subradient_sim_h
#define distributed_gradient_subradient_sim_h



#endif

#include <math.h>

using namespace std;

class InterconnectingMessage {
    
    
public:
    
    double * estimatedProfile;
    double * Tcalcs;
    double * degrees;
    
    double Tcalc;
    int type;
    
    int sender;
    
    
    
    InterconnectingMessage(int dim, int type){
        
        
        
        
        estimatedProfile = new double[dim];
        Tcalcs = new double[dim];
        degrees = new double[dim];
    }    
    
    
    
    
};


typedef struct horizonEstimate {
    
    double ** pastEstimate;
    
} horizonEstimate ;


class ParMod {
    
public:
    
    double Tcalc; 
    double Ts;
    double * idealDegree;
    
    double * weightProfile;
    
    double step_size;
    
    horizonEstimate * estimateCube;
    
    int n;
    
    int id;
    
    double * initialState;
    double * currentProfile;
    double * currentEstimate;
    double ** currentEstimateMatrix;
    double ** pastEstimate;
    double * wProfile;
    double * Tcalcs;
    double * * TcalcsPrediction;
    
    double alfa;
    double beta;
    int siblings;
    
    int hor;
    
    
    
    
    
    ParMod(int _siblings, int _hor){
        
        siblings = _siblings;
        hor = _hor;
        
        currentProfile = new double[siblings];
        
        currentEstimate = new double[siblings];
        
        pastEstimate = new double *[siblings];
        estimateCube = new horizonEstimate[hor];
        currentEstimateMatrix = new double * [hor];
        TcalcsPrediction = new double * [hor];
        
        idealDegree = new double[siblings];
        
        
        for(int h = 0; h < hor; h++){
            
            estimateCube[h].pastEstimate = new double * [siblings];
            currentEstimateMatrix[h] = new double [siblings];
            TcalcsPrediction[h] = new double [siblings];
            
            for (int i = 0; i < siblings; i++) {
                estimateCube[h].pastEstimate[i] = new double[siblings];
                
                TcalcsPrediction[h][i] = 0;
                
            }
        
        }

        
        for (int i = 0; i < siblings ; i ++ ){
            pastEstimate[i] = new double[siblings];
            currentEstimate[i] = 0;
        }
        
        wProfile = new double[siblings];
        
        Tcalcs = new double[siblings];
        
        initialState = new double[siblings];
        
        
        
        
        
        for(int i = 0; i < siblings; i++)  wProfile[i] = ( double) 1 / (double) 3;
        
        step_size = 0.005;
    }
    
    void setPerformanceCosts(double a, double b){ alfa = a; beta = b; }
    
    void setTcalc(double _tcalc){ Tcalc = _tcalc; }
    
    void setTcalc(double _tcalc, int h){ TcalcsPrediction[h][id]  = _tcalc; }
    
    double getIdealDegree(int h){
        
        
        
        return idealDegree[h];
        
    }
    
    void setIdealDegree() { 
        
        idealDegree[0] = sqrtf(alfa * TcalcsPrediction[0][id] / beta); 
    }
    
    
    void setIdealDegree(int h) { 
        
        idealDegree[h] = sqrtf(alfa * TcalcsPrediction[h][id] / beta); 
    }

    
    void Init(double * start_profile, double Tcalcs){
        
        
        
    }
    
    
    void receiveMessage(InterconnectingMessage m){
        
        
        if( m.type == 1 ) {
            
            for(int i = 0; i < siblings; i++) pastEstimate[m.sender][i] = m.estimatedProfile[i];
            
        }
        
        
    }
    
    void subgradient(double * sub_gradient){
        
        
        
        //        double my_Ts = Ts / currentProfile[id];
        
        double currentTs[siblings];
        
        for(int i = 0; i < siblings; i++) currentTs[i] = 0;
        
        int maxIndex = 0;
        
        for (int i = 0; i < siblings; i++) {
            currentTs[i] = Tcalcs[i] / pastEstimate[id][i];
        }
        
        for(int i = 1; i < siblings; i++) if(currentTs[i] > currentTs[maxIndex]) maxIndex = i;
        
        // if i'm the bottleneck
        
        if(maxIndex == id) {
            
            for (int i = 0; i < siblings; i++) sub_gradient[i] = 0;
            
            
            sub_gradient[id] =  (-1) * ( alfa * Tcalcs[id] ) / pow(pastEstimate[id][id], 2) + beta;   
            
            
        }
        
        else {
            
            for (int i = 0; i < siblings; i++) sub_gradient[i] = 0;
            
            sub_gradient[id] = beta;
            sub_gradient[maxIndex] = (-1) * ( alfa * Tcalcs[maxIndex] ) / pow(pastEstimate[id][maxIndex], 2);
            
            
        }
        
        
        
    }
    
    void calculateEstimate(int iteration){
        
        double g[siblings];
        double averageEstimate[siblings];
        
        double partial_sum[siblings];
        for(int i = 0; i < siblings; i++){
            averageEstimate[i] = 0;
            partial_sum[i] = 0;
            
        }
        
        
        double step = 0.00005;
        
        
        // Calculating component-wise the average of each parmod estimate
        
        
        
        for(int i = 0; i < siblings; i++){
            
            for( int j = 0; j < siblings ; j++){
                
                partial_sum[i] += (( wProfile[i] ) * pastEstimate[j][i]);
                
            }
            
            averageEstimate[i] = partial_sum[i];        
            
            
        }
        
        
        subgradient(g); 
        
       
        if(iteration != 0) step_size = step / pow ( (double) iteration, 0.2);
        
        for( int i = 0; i < siblings ; i++){
            
            currentEstimate[i] = averageEstimate[i] - (step_size * g[i]);
            pastEstimate[id][i] = currentEstimate[i];
            
        }
        
        
        
        
    }
    
    
    int receiveProfile( int source, double * estimate){
        
        
        
        for( int i = 0; i < siblings; i++) pastEstimate[source][i] = estimate[i];
        
        
        return 1;
        
    }
    
    int receiveProfile( int source, double * estimate, int h){
        
        
        
        for( int i = 0; i < siblings; i++) estimateCube[h].pastEstimate[source][i] = estimate[i];
        
        
        return 1;
        
    }

    
    
    void calculateEstimate(int iteration, int h){
        
        
        double * g = new double[siblings];
        double * averageEstimate = new double[siblings];
        
        double * partial_sum = new double[siblings];
        for(int i = 0; i < siblings; i++){
            averageEstimate[i] = 0;
            partial_sum[i] = 0;
            
        }

        double step = 0.00005;
        
        // 87.8037
        
        if(iteration != 0 && h == h - 1 ) step_size = step / pow ( (double) iteration, 0.2);

        
        for(int i = 0; i < siblings; i++){
            
            for( int j = 0; j < siblings ; j++){
                
                partial_sum[i] += (( wProfile[i] ) * estimateCube[h].pastEstimate[j][i]);
                
            }
            
            averageEstimate[i] = partial_sum[i];        
            
            
        }
        
            subgradient(g, h);
            
            
            
                        
            for( int i = 0; i < siblings ; i++){
                
                currentEstimate[i] = averageEstimate[i] - (step_size * g[i]);
                estimateCube[h].pastEstimate[id][i] = currentEstimate[i];
            }
            
        
        delete g;
        delete averageEstimate;
        delete partial_sum;
        
        
        
    }
    
    
    
    void subgradient(double * sub_gradient, int h){
        
        
        
        
        double currentTs[siblings];
        double switching_cost = 0;
        
        for(int i = 0; i < siblings; i++) currentTs[i] = 0;
        
        int maxIndex = 0;
        
        for (int i = 0; i < siblings; i++) {
            
            currentTs[i] = TcalcsPrediction[h][i] / estimateCube[h].pastEstimate[id][i];
            
        }
        
        for(int i = 1; i < siblings; i++) if(currentTs[i] > currentTs[maxIndex]) maxIndex = i;
        
        for (int i = 0; i < siblings; i++) sub_gradient[i] = 0;
        
        // calculate switching cost
        
        
        if( h == 0 && h == hor - 1 ) switching_cost = ( (2 * estimateCube[h].pastEstimate[id][id]) - (2 * initialState[id] ));
        
        else if (h == 0 && h != hor - 1 ) switching_cost = ( (4 * estimateCube[h].pastEstimate[id][id]) - (2 * initialState[id] )) - (2 * estimateCube[h+1].pastEstimate[id][id]);
        
        else if (h == hor-1) switching_cost = (2 * estimateCube[h].pastEstimate[id][id]) - (2 * estimateCube[h-1].pastEstimate[id][id]);
        
                   
            else switching_cost =  ( (4 * estimateCube[h].pastEstimate[id][id]) - (2 * estimateCube[h-1].pastEstimate[id][id])) - (2 * estimateCube[h+1].pastEstimate[id][id]);
                
        // if i'm the bottleneck
        
//        switching_cost = 1;
        
        if(maxIndex == id) {
            
            
            sub_gradient[id] =  (-1) * ( alfa * TcalcsPrediction[h][id] ) / pow(estimateCube[h].pastEstimate[id][id], 2) + (beta * switching_cost);   
            
            
        }
        
        else {
            
            sub_gradient[id] = beta * switching_cost;
            sub_gradient[maxIndex] = (-1) * ( alfa * TcalcsPrediction[h][maxIndex] ) / pow(estimateCube[h].pastEstimate[id][maxIndex], 2);
            
        }
        
                
        
    }
    
    
    double getCost(){
        
        
        double cost = 0;
        
        int maxIndex[ hor ];
        double Tserv[hor];
        double performance_cost = 0;
        double switching_cost = 0;
        
        for (int h = 0; h < hor; h++) maxIndex[h] = 0;
        
        for(int h = 0; h < hor; h++){
        
            
            
            for (int i = 1;  i < siblings; i++) {
                
                float tcalc1, tcalc2 = 0;
                
                tcalc1 = TcalcsPrediction[h][i] / (estimateCube[h].pastEstimate[id][i]);
                tcalc2 = TcalcsPrediction[h][ maxIndex[h] ] / (estimateCube[h].pastEstimate[id][maxIndex[h]]);
                
                if(tcalc1 > tcalc2) maxIndex[h] = i;
                
            }
            
            Tserv[h] = TcalcsPrediction[h][maxIndex[h]] / estimateCube[h].pastEstimate[id][maxIndex[h]];
            
//            if( h == 0 ) switching_cost += pow( (estimateCube[h].pastEstimate[id][id] - initialState[id]), 2 ); 
            
            performance_cost += (alfa * Tserv[h]);


            
        }
        
        
       if(hor == 2) switching_cost = beta * ( pow ( estimateCube[0].pastEstimate[id][id] - initialState[id], 2) + pow ( estimateCube[1].pastEstimate[id][id] - initialState[id], 2));
        
        
       else switching_cost = beta * ( pow ( estimateCube[0].pastEstimate[id][id] - initialState[id], 2));           
        
        
        cost = performance_cost + switching_cost;
        
        // cost without switching cost
//        cost = performance_cost + beta * ( estimateCube[0].pastEstimate[id][id] + estimateCube[1].pastEstimate[id][id] );
        
        return cost;
       
        
        
    }

    
    
    
    
    
    
    
    
};


class Communicator {
    
public:
    
    int  sendMessage(ParMod& source, ParMod& dest, InterconnectingMessage& m){
        
        
        for(int i = 0; i < source.siblings ; i++) dest.pastEstimate[source.id][i] = m.estimatedProfile[i];
        
        
        
        return 1;
        
    }
    
    
    
    
    
};



